package map;

import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author Yuri Denison
 * @since 08.08.11
 */
public class MyHashMap implements Map {
    private static final int DEFAULT_CAPACITY = 16;
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;

    private int capacity;
    private int threshold;
    private final float loadFactor;
    private int size;

    private volatile List<Object, Object>[] keys;
    private static final Set<Object> KEY_SET = new HashSet<Object>();
    private static final Set<List.Cell> ENTRY_SET = new HashSet<List.Cell>();

    public MyHashMap() {
        this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

    public MyHashMap(int capacity) {
        this(capacity, DEFAULT_LOAD_FACTOR);
    }

    public MyHashMap(int capacity, final float loadFactor) {
        this.capacity = capacity;
        this.loadFactor = loadFactor;
        threshold = (int) (capacity * loadFactor);
        keys = new List[capacity];
        size = 0;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public boolean containsKey(Object key) {
        return keys[getKeyPosition(key)].isEmpty();
    }

    private int getKeyPosition(Object key) {
        return (key == null) ? 0 : (key.hashCode() & (capacity - 1));
    }

    @Override
    public boolean containsValue(Object value) {
        for (List<Object, Object> list : keys) {
            if (list.containsValue(value)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Object get(Object key) {
        List<Object, Object> curList = keys[getKeyPosition(key)];
        return ((curList != null) ? curList.get(key) : null);
    }

    @Override
    public Object put(Object key, Object value) {
        int hash;
        if (size > threshold) {
            rehash();
        }
        hash = getKeyPosition(key);
        if (keys[hash] == null) {
            keys[hash] = new List<Object, Object>();
        }
        size++;
        return keys[hash].add(key, value);
    }

    @Override
    public Object remove(Object key) {
        int hash = getKeyPosition(key);
        List<Object, Object> curList = keys[hash];
        if (curList != null) {
            Object o;
            if ((o = curList.remove(key)) != null) {
                size--;
            }
            if(curList.isEmpty()) {
                keys[hash] = null;
            }
            return o;
        } else {
            return null;
        }
    }

    @Override
    public void putAll(Map m) {
        Set<List.Cell> set = m.entrySet();
        for (List.Cell cell : set) {
            put(cell.getKey(), cell.getValue());
        }
    }

    @Override
    public void clear() {
        for (List list : keys) {
            if (list != null) {
                list.clear();
            }
        }
    }

    @Override
    public Set<Object> keySet() {
        for (List<Object, Object> list : keys) {
            for (List.Cell cell : list) {
                KEY_SET.add(cell.getKey());

            }
        }
        return KEY_SET;
    }

    @Override
    public Collection values() {
        throw new UnsupportedOperationException();
    }

    @Override
    public Set<List.Cell> entrySet() {
        return ENTRY_SET;
    }

    private void rehash() {
        List<Object, Object>[] copy = new List[keys.length];
        System.arraycopy(keys, 0, copy, 0, keys.length);
        capacity *= 2;
        size = 0;
        threshold = (int) (capacity * loadFactor);
        keys = new List[capacity];
        for (List<Object, Object> list : copy) {
            if (list != null) {
                for (List.Cell cell : list) {
                    if (cell.getKey() != null) {
                        put(cell.getKey(), cell.getValue());
                    }
                }
            }
        }
    }


}
